package com.gitee.wsl.compose.chart.xy.axis

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import com.gitee.wsl.common.ui.base.ColorOrBrush
import com.gitee.wsl.common.ui.base.toColorOrBrush
import com.gitee.wsl.common.ui.draw.node.LineNode
import com.gitee.wsl.common.ui.draw.node.PathNode
import com.gitee.wsl.common.ui.draw.node.TextNode
import com.gitee.wsl.common.ui.theme.local.Web
import com.gitee.wsl.common.ui.draw.type.Orient
import com.gitee.wsl.common.ui.draw.type.TextHAlign
import com.gitee.wsl.common.ui.draw.type.TextVAlign
import com.gitee.wsl.mathematics.function.scale.FirstLastRange
import com.gitee.wsl.mathematics.function.scale.discrete.banded.BandedScale
import com.gitee.wsl.mathematics.function.scale.Scale
import com.gitee.wsl.mathematics.function.scale.Tickable
import kotlin.math.round

class AxisElement<D>(
    val orient: Orient,
    val scale: FirstLastRange<D, Float>
) {
    var tickValues: List<D> = listOf()
    var tickSizeInner: Float = 6.0f
    var tickSizeOuter: Float = 6.0f
    var tickPadding: Float = 3.0f
    var axisStroke: ColorOrBrush? = Color.Web.black.toColorOrBrush()
    var axisStrokeWidth: Float? = 1.0f
    var tickStroke: ColorOrBrush? = Color.Web.black.toColorOrBrush()
    var tickStrokeWidth: Float? = 1.0f
    var fontSize: Float = 12.0f
    var fontColor: ColorOrBrush? = Color.Web.black.toColorOrBrush()
    var fontFamily: FontFamily = FontFamily.SansSerif
    var fontWeight: FontWeight = FontWeight.Normal
    var fontStyle: FontStyle = FontStyle.Normal
    var tickFormat: (D) -> String = { n: D -> n.toString() }

    private val k = if (orient == Orient.TOP || orient == Orient.LEFT) -1 else 1

    /* private fun center(scale: BandedScale<D>): (D) -> Double {
         var offset = (scale.bandwidth - 1).coerceAtLeast(0.0) / 2 // Adjust for 0.5px offset.
         if (scale.round) offset = round(offset)
         return { d: D -> (+ scale(d) + offset) }
     }

     private fun number(scale: Scale<D, Float>): (D) -> Float = { scale(it) }*/

    /*@Suppress("UNCHECKED_CAST")
    internal fun build(content: GroupNode) {
        val values: List<D> = if (tickValues.isEmpty() && scale is Tickable<*>) scale.ticks() as List<D> else tickValues
        val spacing = tickSizeInner.coerceAtLeast(0.0f) + tickPadding
        val start = scale.start()
        val end = scale.end()
        val position = if (scale is BandedScale) center(scale) else number(scale)

        with(content) {

            // the main axis line
            if (axisStroke != null && axisStrokeWidth != null) {
                path {
                    strokeColor = axisStroke
                    strokeWidth = axisStrokeWidth
                    fill = null

                    if (orient.isVertical()) {
                        moveTo(tickSizeOuter * k, start)
                        lineTo(.0f, start)
                        lineTo(.0f, end)
                        lineTo(tickSizeOuter * k, end)
                    } else {
                        moveTo(start, tickSizeOuter * k)
                        lineTo(start, .0f)
                        lineTo(end, .0f)
                        lineTo(end, tickSizeOuter * k)
                    }
                }
            }

            // each tick and associated label
            values.forEach { value ->
                group {
                    transform {
                        if (orient.isHorizontal())
                            translate(x = position(value).toDouble())
                        else
                            translate(y = position(value).toDouble())
                    }
                    if (tickStroke != null && tickStrokeWidth != null) {
                        if (orient.isHorizontal())
                            line(y2 = k * tickSizeInner) {
                                strokeColor = tickStroke
                                strokeWidth = tickStrokeWidth
                            }
                        else
                            line( x2 = k * tickSizeInner) {
                                strokeColor = tickStroke
                                strokeWidth = tickStrokeWidth
                            }
                    }
                    if (fontColor != null) {
                        text(text = tickFormat(value)) {
                            textColor = fontColor
                            fontWeight = this@AxisElement.fontWeight
                            fontSize = this@AxisElement.fontSize
                            fontFamily = this@AxisElement.fontFamily
                            fontStyle = this@AxisElement.fontStyle
                            hAlign = when (orient) {
                                Orient.LEFT -> TextHAlign.RIGHT
                                Orient.RIGHT -> TextHAlign.LEFT
                                else -> TextHAlign.MIDDLE
                            }

                            vAlign = when (orient) {
                                Orient.TOP -> TextVAlign.BASELINE
                                Orient.BOTTOM -> TextVAlign.HANGING
                                else -> TextVAlign.MIDDLE
                            }
                            if (orient.isHorizontal())
                                y = spacing * k
                            else
                                x = spacing * k
                            //textContent = tickFormat(it)
                        }
                    }
                }
            }
        }
    }*/
}

internal fun <D> AxisElement<D>.toAxisAnnotations(): List<AxisAnnotation<D>> {
    val sign = if (orient == Orient.TOP || orient == Orient.LEFT) -1 else 1
    val values: List<D> = if (tickValues.isEmpty() && scale is Tickable<*>) {
        (scale as Tickable<*>).ticks() as List<D>
    } else {
        tickValues
    }
    val spacing = tickSizeInner.coerceAtLeast(0.0f) + tickPadding

    return values.map { data ->
        var line: LineNode? = null
        var textNode: TextNode? = null
        if (tickStroke != null && tickStrokeWidth != null) {
            line = if (orient.isHorizontal()) {
                LineNode(y2 = sign * tickSizeInner).apply {
                    strokeColor = tickStroke
                    strokeWidth = tickStrokeWidth
                }
            } else {
                LineNode(x2 = sign * tickSizeInner).apply {
                    strokeColor = tickStroke
                    strokeWidth = tickStrokeWidth
                }
            }
        }
        if (fontColor != null) {
            textNode = TextNode().apply {
                textColor = fontColor
                fontWeight = this@toAxisAnnotations.fontWeight
                fontSize = this@toAxisAnnotations.fontSize
                fontFamily = this@toAxisAnnotations.fontFamily
                fontStyle = this@toAxisAnnotations.fontStyle
                hAlign = when (orient) {
                    Orient.LEFT -> TextHAlign.RIGHT
                    Orient.RIGHT -> TextHAlign.LEFT
                    else -> TextHAlign.MIDDLE
                }

                vAlign = when (orient) {
                    Orient.TOP -> TextVAlign.BASELINE
                    Orient.BOTTOM -> TextVAlign.HANGING
                    else -> TextVAlign.MIDDLE
                }
                if (orient.isHorizontal()) {
                    y = spacing * sign
                } else {
                    x = spacing * sign
                }
                textContent = tickFormat(data)
            }
        }
        AxisAnnotation(data, line, textNode)
    }
}


private fun <D> center(scale: BandedScale<D>): (D) -> Float {
    var offset = (scale.bandwidth.toFloat() - 1).coerceAtLeast(0.0f) / 2 // Adjust for 0.5px offset.
    if (scale.round) offset = round(offset)
    return { d: D -> +scale(d).toFloat() + offset }
}

private fun <D> number(scale: Scale<D, Float>): (D) -> Float = { scale(it) }

internal fun <D> AxisElement<D>.position(d: D): Float = (scale as? BandedScale)?.let { center(it)(d) } ?: number(scale)(d)

internal fun <D> AxisElement<D>.toAxisLine(): PathNode? {
    val start = scale.start()
    val end = scale.end()
    val sign = if (orient == Orient.TOP || orient == Orient.LEFT) -1 else 1
    return if (axisStroke != null && axisStrokeWidth != null) {
        PathNode(Path()).apply {
            strokeColor = axisStroke
            strokeWidth = axisStrokeWidth
            fill = null

            if (orient.isVertical()) {
                moveTo(tickSizeOuter * sign, start)
                lineTo(.0f, start)
                lineTo(.0f, end)
                lineTo(tickSizeOuter * sign, end)
            } else {
                moveTo(start, tickSizeOuter * sign)
                lineTo(start, .0f)
                lineTo(end, .0f)
                lineTo(end, tickSizeOuter * sign)
            }
        }
    } else {
        null
    }
}

